﻿using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.OpenApi.Models;
using Ebusiness_Authservice.Data;
using Volo.Abp;
using Volo.Abp.Uow;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.AspNetCore.Serilog;
using Volo.Abp.AuditLogging.EntityFrameworkCore;
using Volo.Abp.Autofac;
using Volo.Abp.AutoMapper;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.MySQL;
using Volo.Abp.Localization;
using Volo.Abp.Localization.ExceptionHandling;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Swashbuckle;
using Volo.Abp.Security.Claims;
using Volo.Abp.UI.Navigation.Urls;
using Volo.Abp.Validation.Localization;
using Volo.Abp.VirtualFileSystem;
//using Volo.Abp.OpenIddict.EntityFrameworkCore;
//using Volo.Abp.OpenIddict;
//using Volo.Abp.Identity.EntityFrameworkCore;
//using Volo.Abp.Identity;
//using Volo.Abp.PermissionManagement.EntityFrameworkCore;
//using Volo.Abp.PermissionManagement.HttpApi;
//using Volo.Abp.PermissionManagement;
//using Volo.Abp.Account.Web;
//using Volo.Abp.Account;
//using Volo.Abp.PermissionManagement.Identity;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc.AntiForgery;
using Volo.Abp.AspNetCore.Mvc.UI.Theming;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite;
using OpenIddict.Abstractions;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using OpenIddict.Server.AspNetCore;
using Volo.Abp.Authorization.Permissions;
using Ebusiness_Authservice.Permission;
using System.Security.Cryptography.X509Certificates;
using Consul;

namespace Ebusiness_Authservice;

[DependsOn(
    // ABP Framework packages
    typeof(AbpAspNetCoreMvcModule),
    typeof(AbpAutofacModule),
    typeof(AbpAutoMapperModule),
    typeof(AbpEntityFrameworkCoreMySQLModule),
    typeof(AbpSwashbuckleModule),
    typeof(AbpAspNetCoreSerilogModule)

#region  分布式权限系统  openddict 需要集成的应用模块
// Audit logging module packages
//typeof(AbpAuditLoggingEntityFrameworkCoreModule)

//, typeof(AbpOpenIddictEntityFrameworkCoreModule) //分布式认证模块
//, typeof(AbpOpenIddictAspNetCoreModule) // 集成Volo.Abp.OpenIddict.AspNetCore

// 集成AbpIdentity
//, typeof(AbpIdentityApplicationModule),
//typeof(AbpIdentityHttpApiModule),
//typeof(AbpIdentityEntityFrameworkCoreModule)

// 集成AbpPermissionManagement
//, typeof(AbpPermissionManagementApplicationModule),
//typeof(AbpPermissionManagementHttpApiModule),
//typeof(AbpPermissionManagementEntityFrameworkCoreModule)
//   , typeof(AbpPermissionManagementDomainIdentityModule)
// 集成AbpAccount
//, typeof(AbpAccountApplicationModule),
//typeof(AbpAccountHttpApiModule),
//typeof(AbpAccountWebOpenIddictModule)

, typeof(AbpAspNetCoreMvcUiBasicThemeModule)
//Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite
//, typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule) // 必须有
#endregion

)]
public class Ebusiness_AuthserviceModule : AbpModule
{
    /* Single point to enable/disable multi-tenancy */
    private const bool IsMultiTenant = true;

    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        var hostingEnvironment = context.Services.GetHostingEnvironment();
        var configuration = context.Services.GetConfiguration();
    }

    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        var hostingEnvironment = context.Services.GetHostingEnvironment();
        var configuration = context.Services.GetConfiguration();

        //配置自定义控制器
        ConfigurControllers(context);
        ConfigureAuthentication(context);
        ConfigureUrls(configuration);
        ConfigureAutoMapper(context);
        ConfigureSwagger(context.Services, configuration);
        ConfigureAutoApiControllers();
        ConfigureVirtualFiles(hostingEnvironment);
        ConfigureCors(context, configuration);
        ConfigureEfCore(context);

        //取消所有的权限校验
        //context.Services.AddAlwaysAllowAuthorization();

        //关闭xss攻击
        ConfigAntiforgery();

        //配置主题
        //ConfigTheme();

        //配置 OpenIddict
        ConfigOpenIddict(context, configuration);

        //配置缓存服务
        // 在builder.Services配置部分添加
        ConfigMemoryCache(context);
    }

    /// <summary>
    /// 配置缓存服务
    /// </summary>
    private void ConfigMemoryCache(ServiceConfigurationContext context)
    {
        context.Services.AddMemoryCache(); // 添加内存缓存服务

        context.Services.AddDistributedMemoryCache(); // Session 依赖缓存
        context.Services.AddSession(options =>
        {
            options.IdleTimeout = TimeSpan.FromMinutes(30); // 会话超时时间
            options.Cookie.HttpOnly = true;                 // 安全
            options.Cookie.IsEssential = true;              // GDPR 必须
        });
    }

    /// <summary>
    /// 配置 OpenIddict
    /// </summary>
    /// <param name="context"></param>
    private void ConfigOpenIddict(ServiceConfigurationContext context, IConfiguration configuration)
    {
        // 配置OpenIddict身份认证服务
        context.Services.AddOpenIddict()
            .AddCore(options =>
            {
                // 配置OpenIddict核心服务
                options.UseEntityFrameworkCore()
                       .UseDbContext<Ebusiness_AuthserviceDbContext>();
            })
            .AddServer(options =>
            {
                // 设置授权码的过期时间（默认10分钟）
                options.SetAuthorizationCodeLifetime(TimeSpan.FromMinutes(20));

                // 设置访问令牌过期时间
                options.SetAccessTokenLifetime(TimeSpan.FromHours(1));

                // 设置刷新令牌过期时间
                options.SetRefreshTokenLifetime(TimeSpan.FromMinutes(30));
                // 配置各种端点的URL
                options
                    .SetTokenEndpointUris("/connect/token")           // 令牌端点：用于获取访问令牌
                    .SetAuthorizationEndpointUris("/connect/authorize") // 授权端点：用于用户授权  用户授权页面，用户同意授权 Scope
                    .SetUserInfoEndpointUris("/connect/userinfo")     // 用户信息端点：获取用户信息
                    .SetIntrospectionEndpointUris("/connect/introspect") // 令牌内省端点：验证令牌有效性  //  Introspection Endpoint 默认 OpenIddict 内部处理，不支持 EnableXXXPassthrough() 方法。
                    .SetEndSessionEndpointUris("connect/endsession")  //结束会话端点：用户登出  
                                                                      //.SetEndUserVerificationEndpointUris("connect/verify")   // 用户验证端点：Device Flow 授权
                    .SetRevocationEndpointUris("/connect/revoke");    // 令牌回收端点：回收令牌 回收 Access Token / Refresh Toke  //  Token 回收默认处理，不支持透传

                // 允许的授权流程类型
                //options
                //    .AllowPasswordFlow()        // 密码模式：用户名密码直接获取令牌
                //    .AllowRefreshTokenFlow()    // 刷新令牌模式：使用刷新令牌获取新的访问令牌
                //    .AllowClientCredentialsFlow(); // 客户端凭据模式：应用间通信

                //options.AcceptAnonymousClients();  //接受所有的客户端

                options
                      .AllowAuthorizationCodeFlow()  //授权码模式 
                      .AllowHybridFlow()   //结合授权码和隐式流程的混合模式 ,用户授权后，同时返回授权码和访问令牌,可以立即获得访问令牌，也可以使用授权码获取刷新令牌
                                           //.AllowImplicitFlow()
                      .AllowPasswordFlow()  //密码模式：用户名密码直接获取令牌
                      .AllowClientCredentialsFlow()  // 客户端凭据模式：应用间通信
                      .AllowRefreshTokenFlow() // 刷新令牌模式：使用刷新令牌获取新的访问令牌
                      .AllowNoneFlow();  //允许不进行任何授权流程的访问

                // 加密和签名配置
                //options
                //    .AddEphemeralEncryptionKey()  // 添加临时加密密钥（生产环境应使用持久密钥）
                //    .AddEphemeralSigningKey()     // 添加临时签名密钥
                //    .DisableAccessTokenEncryption(); // 禁用访问令牌加密（简化示例）

                //options
                //     .AddSigningKey(new SymmetricSecurityKey(
                //         Encoding.UTF8.GetBytes("u1D8fJkH2M3g8sV9tZxYqP4mC7lN0bR6"))) //为 Token 生成 签名密钥
                //     .AddEncryptionKey(new SymmetricSecurityKey(
                //         Encoding.UTF8.GetBytes("u1D8fJkH2M3g8sV9tZxYqP4mC7lN0bR6")))  //为 Token 生成 加密密钥  
                //     .DisableAccessTokenEncryption(); // 可选  禁用对 Access Token 的加密   Access Token 仅签名，可读 Payload，方便调试
                var path = "";
                var conf = configuration["ASPNETCORE_ENVIRONMENT"];
                if (conf == "Development")
                    path = Path.Combine(Directory.GetCurrentDirectory(), "authserver.pfx");
                else
                    path = "/https/authserver.pfx";

                options
                     //.AddDevelopmentSigningCertificate()
                     //.AddDevelopmentEncryptionCertificate()
                     .AddSigningCertificate(new X509Certificate2(path, "123456"))
                     .AddEncryptionCertificate(new X509Certificate2(path, "123456"))
                     .DisableAccessTokenEncryption();
                // 注册作用域（权限范围）
                //RegisterScopes = 在服务端声明允许的作用域列表，客户端才能在 scope 参数中申请这些权限。
                options.RegisterScopes(new[]
                {
                    OpenIddictConstants.Scopes.OpenId,
                    OpenIddictConstants.Scopes.Email,
                    OpenIddictConstants.Scopes.Profile,
                    OpenIddictConstants.Scopes.Phone,
                    OpenIddictConstants.Scopes.Roles,
                    OpenIddictConstants.Scopes.Address,
                    OpenIddictConstants.Scopes.OfflineAccess
                });

                // Introspection Endpoint 默认 OpenIddict 内部处理，不支持 EnableXXXPassthrough() 方法。
                //当端点收到请求时，不直接由 OpenIddict 内部处理，而是传给你的 Controller 或中间件
                options.UseAspNetCore()  //启用 OpenIddict 的 ASP.NET Core 集成
                       .EnableAuthorizationEndpointPassthrough()  //授权请求透传，可自定义授权页面
                       .EnableTokenEndpointPassthrough()   //Token 请求透传，可自定义颁发逻辑
                       .EnableUserInfoEndpointPassthrough()   //用户信息请求透传，可自定义返回 Claims
                       .EnableEndSessionEndpointPassthrough()  //用户登出请求透传
                                                               //.EnableEndUserVerificationEndpointPassthrough()  //Device Flow 用户验证请求透传
                       .EnableStatusCodePagesIntegration();  //将 OpenIddict 的 401/403/400 错误交给 ASP.NET Core StatusCodePages 处理
            })
            //验证 Access Token（访问令牌）是否有效
            //检查 Token 是否有效、过期、撤销
            .AddValidation(options =>
            {
                // 配置令牌验证
                options.UseLocalServer();    // 使用本地服务器验证,通过本地 OpenIddict 服务器验证 Token
                //自动拦截请求中的 Access Token（比如 Authorization: Bearer xxx）
                //验证 Token 是否有效
                //把 Token 对应的用户信息（Claims）填充到 HttpContext.User
                options.UseAspNetCore();     // 集成ASP.NET Core, 将 OpenIddict 的验证功能 集成到 ASP.NET Core 中间件
            });

        context.Services.AddAuthentication(options =>
         {
             options.DefaultScheme = "Cookies";          // 默认身份验证方案
             options.DefaultChallengeScheme = "oidc";    // 如果用 OIDC 也可以设置
         })
        .AddCookie("Cookies");

        // 添加认证服务
        //context.Services.AddAuthentication(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);

        // 添加授权服务
        context.Services.AddAuthorization();
    }

    /// <summary>
    /// 配置自定义控制器
    /// </summary>
    private void ConfigurControllers(ServiceConfigurationContext context)
    {
        ////配置自定义过滤器
        //context.Services.AddControllers(option =>
        //{
        //    //添加自定义过滤器
        //    //option.Filters.Add<UnitOfWorkFilter>();
        //    //全局取消默认token校验 忽略xss攻击
        //    //option.Filters.Add(new IgnoreAntiforgeryTokenAttribute());
        //});

        // 添加 MVC 支持（Controller + Views）
        // 注册 MVC 控制器和 Razor 页面
        context.Services.AddControllersWithViews();
        context.Services.AddRazorPages();

        //servicesout configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
        context.Services.AddEndpointsApiExplorer(); // 添加API端点探索器
    }

    /// <summary>
    /// 配置主题
    /// </summary>
    private void ConfigTheme()
    {
        Configure<AbpThemingOptions>(options =>
        {
            //options.DefaultThemeName = "Basic"; // 或 "LeptonXLite"
            options.DefaultThemeName = "LeptonXLite"; // 或 "LeptonXLite"
        });
    }

    /// <summary>
    /// 关闭xss攻击
    /// </summary>
    private void ConfigAntiforgery()
    {
        Configure<AbpAntiForgeryOptions>(option =>
        {
            option.AutoValidate = false;
        });
    }

    /// <summary>
    /// 配置鉴权
    /// </summary>
    /// <param name="context"></param>
    private void ConfigureAuthentication(ServiceConfigurationContext context)
    {
        //context.Services.Configure<AbpClaimsPrincipalFactoryOptions>(options =>
        //{
        //    options.IsDynamicClaimsEnabled = true;
        //});


        //配置自定义鉴权类
        Configure<AbpPermissionOptions>(option =>
        {
            //当注册多个权限值提供者时，PermissionGrantResult.Prohibited;
            //当其中有一个返回值为Prohibited，会直接返回403，授权没有通过
            //当返回值没有Prohibited,其中有一个返回值为Granted,则代表授权通过
            //当返回值全部为Undefined,,则代表授权没有通过
            //option.ValueProviders.Add(typeof(CustomPermissionValueProvider));
            //option.ValueProviders.Add(typeof(ProductPermissionValueProvider));
            option.ValueProviders.Add(typeof(CustomPermissionValueProvider));
            //option.ValueProviders.Clear();
        });
    }


    private void ConfigureUrls(IConfiguration configuration)
    {
        Configure<AppUrlOptions>(options =>
        {
            options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"];
            options.RedirectAllowedUrls.AddRange(configuration["App:RedirectAllowedUrls"]?.Split(',') ?? Array.Empty<string>());
        });
    }


    private void ConfigureVirtualFiles(IWebHostEnvironment hostingEnvironment)
    {
        Configure<AbpVirtualFileSystemOptions>(options =>
        {
            options.FileSets.AddEmbedded<Ebusiness_AuthserviceModule>();
            if (hostingEnvironment.IsDevelopment())
            {
                /* Using physical files in development, so we don't need to recompile on changes */
                options.FileSets.ReplaceEmbeddedByPhysical<Ebusiness_AuthserviceModule>(hostingEnvironment.ContentRootPath);
            }
        });
    }

    private void ConfigureAutoApiControllers()
    {
        Configure<AbpAspNetCoreMvcOptions>(options =>
        {
            options.ConventionalControllers.Create(typeof(Ebusiness_AuthserviceModule).Assembly);
        });
    }

    private void ConfigureSwagger(IServiceCollection services, IConfiguration configuration)
    {
        //services.AddAbpSwaggerGenWithOAuth(
        //    configuration["AuthServer:Authority"]!,
        //    new Dictionary<string, string>
        //    {
        //            {"Ebusiness_Authservice", "Ebusiness_Authservice API"}
        //    },
        //    options =>
        //    {
        //        options.SwaggerDoc("v1", new OpenApiInfo { Title = "Ebusiness_Authservice API", Version = "v1" });
        //        options.DocInclusionPredicate((docName, description) => true);
        //        options.CustomSchemaIds(type => type.FullName);
        //    });

        services.AddAbpSwaggerGen(
                option =>
                {
                    option.SwaggerDoc("v1", new OpenApiInfo { Title = "Ebusiness_Authservice API", Version = "v1" });
                    option.DocInclusionPredicate((docName, description) => true);
                    option.CustomSchemaIds(type => type.FullName);

                    //添加token验证
                    option.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
                    {
                        Description = "请输入token,格式为 Bearer xxxxxxxx",
                        Name = "Authorization", //标头名
                        In = ParameterLocation.Header,  //表示头部
                        Type = SecuritySchemeType.ApiKey,
                        BearerFormat = "JWT",  //token
                        Scheme = "Bearer"
                    });
                    //添加验证条件
                    option.AddSecurityRequirement(new OpenApiSecurityRequirement
                        {
                              {
                                //Id = "Bearer" 表示引用的安全方案的 ID 是 "Bearer"，这个 ID 必须与之前通过 AddSecurityDefinition 定义的安全方案一致。
                                //这部分代码的作用是告诉 Swagger 使用之前定义的 "Bearer" 安全方案。
                                new OpenApiSecurityScheme{ Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id ="Bearer" } },
                                new string[]{ }//不设权限
                              }
                        });
                    //option.SwaggerDoc("v3",
                    //    new OpenApiInfo
                    //    {
                    //        Title = typeof(Ebusiness_AuthserviceModule).Assembly.GetName().Name + "哈哈哈", //组名字，右上角可以选择
                    //        Version = "v1",//版本
                    //        Description = $"接口文档说明，放这里 ",
                    //        Contact = new OpenApiContact()
                    //        {
                    //            Name = "zhangsan",//名字
                    //            Email = "xxx@qq.com",//邮箱
                    //            Url = null
                    //        }//通常这里都是放联系，地址的，一般不用
                    //    });
                    //显示Abp自动api接口
                    //option.DocInclusionPredicate((x, y) => true);
                    //option.CustomSchemaIds(ty => ty.FullName);
                    // true : 设置控制器层注释，对控制器中方法的注释的显示
                    // xml文档绝对路径,"WebApplication2"为项目名，表示读取该项目的xml文件
                    //将上面代码放进去会报错，因为我们设置了xml文档地址：开启方法》右键项目》属性》生成》输出》打开“生成包含api的文档的文件”之后就可以直接运行了。
                    //var x = typeof(Ebusiness_AuthserviceModule).Assembly.GetName().Name;
                    //var file = Path.Combine(AppContext.BaseDirectory, $"{x}.xml");
                    //// xml文档绝对路径
                    ////var path = Path.Combine(AppContext.BaseDirectory, file);
                    //option.IncludeXmlComments(file, true);
                    ////加载别的程序集文档文件
                    //option.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, $"{typeof(Ebusiness_AuthserviceModule).Assembly.GetName().Name}.xml"), true);
                    //// 对方法名的名称进行排序，首字母顺序。
                    //option.OrderActionsBy(o => o.RelativePath);
                });
    }

    private void ConfigureAutoMapper(ServiceConfigurationContext context)
    {
        context.Services.AddAutoMapperObjectMapper<Ebusiness_AuthserviceModule>();
        Configure<AbpAutoMapperOptions>(options =>
        {
            /* Uncomment `validate: true` if you want to enable the Configuration Validation feature.
             * See AutoMapper's documentation to learn what it is:
             * https://docs.automapper.org/en/stable/Configuration-validation.html
             */
            options.AddMaps<Ebusiness_AuthserviceModule>(/* validate: true */);
        });
    }

    private void ConfigureCors(ServiceConfigurationContext context, IConfiguration configuration)
    {
        context.Services.AddCors(options =>
        {
            options.AddDefaultPolicy(builder =>
            {
                builder
                    .WithOrigins(
                        configuration["App:CorsOrigins"]?
                            .Split(",", StringSplitOptions.RemoveEmptyEntries)
                            .Select(o => o.RemovePostFix("/"))
                            .ToArray() ?? Array.Empty<string>()
                    )
                    .WithAbpExposedHeaders()
                    .SetIsOriginAllowedToAllowWildcardSubdomains()
                    .AllowAnyHeader()
                    .AllowAnyMethod()
                    .AllowCredentials();
            });
        });
    }


    private void ConfigureEfCore(ServiceConfigurationContext context)
    {
        context.Services.AddAbpDbContext<Ebusiness_AuthserviceDbContext>(options =>
        {
            /* You can remove "includeAllEntities: true" to create
             * default repositories only for aggregate roots
             * Documentation: https://docs.abp.io/en/abp/latest/Entity-Framework-Core#add-default-repositories
             */
            options.AddDefaultRepositories(includeAllEntities: true);
        });

        Configure<AbpDbContextOptions>(options =>
        {
            options.Configure(configurationContext =>
            {
                configurationContext.UseMySQL();
            });
        });

    }

    public override void OnApplicationInitialization(ApplicationInitializationContext context)
    {
        var app = context.GetApplicationBuilder();
        var env = context.GetEnvironment();

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        //app.UseAbpRequestLocalization();

        //app.UseCorrelationId();
        app.UseStaticFiles();
        app.UseRouting();
        app.UseCors();
        app.UseAuthentication();

        app.UseUnitOfWork();
        //app.UseDynamicClaims();
        app.UseAuthorization();

        app.UseSwagger();
        app.UseAbpSwaggerUI(options =>
        {
            options.SwaggerEndpoint("/swagger/v1/swagger.json", "Ebusiness_Authservice API");

            var configuration = context.GetConfiguration();
            //options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]);
            //options.OAuthScopes("Ebusiness_Authservice");
        });

        app.UseSession(); // 必须在 UseEndpoints 之前

        //app.UseAuditing();
        //app.UseAbpSerilogEnrichers();
        app.UseConfiguredEndpoints();
    }
}
